/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

// ============================================================================================================================
#include <dylab/simobject/SimObject.h>
#include <dylab/simulation/Simulation.h>
#include <dylab/simobject/ComModelStructureLoader.h>
#include <dylab/utils/geometry/Vector4.h>

// ============================================================================================================================
void dylab::SimObject::registerAllComponentTypes()
{
	ComModelStructure::registerComponentType("ModelStructure");
	ComModelStructureLoader::registerComponentType("ModelStructureLoader");
	ComModelIntegrator::registerComponentType("ModelIntegrator");
	ComModelTechnique::registerComponentType("ModelTechnique");
}
//-----------------------------------------------------------------------------
void dylab::SimObject::registerAllEntities()
{
	registerEntity("simObject");
    ComModelStructure::registerEntity("structure");
	ComModelStructureLoader::registerEntity("loader");
    ComModelIntegrator::registerEntity("integrator");
	ComModelTechnique::registerEntity("technique");
}

// ============================================================================================================================
dylab::SimObject::SimObject(Simulation * parentSimulation, const String & name)
: mParentSimulation(parentSimulation), mName(name)
{
    // check argument
    DYLAB_ASSERT(mParentSimulation != NULL);
}
//-----------------------------------------------------------------------------
dylab::SimObject::~SimObject()
{ 
    mModelTechnique.destroy();
    mModelIntegrator.destroy();

    // destroy model renderer, if it exists
    ComGfx::IScene::ScopedExLock scene(mParentSimulation->getGfx());
    if (scene->findModelRenderer(mName))
        scene->destroyModelRenderer(mName);

    mModelStructure.destroy();
}
//-----------------------------------------------------------------------------
dylab::ComModelStructure::Instance * dylab::SimObject::createModelStructure(const String & _modelName)
{ 
    // create the structure
    mModelStructure = ComModelStructure::create(_modelName); 

    // initialize the structure
    ComModelStructure::ScopedExLock(&mModelStructure)->initialize(this);

    // return the created structure instance
    return getModelStructure();
}
//-----------------------------------------------------------------------------
void dylab::SimObject::createModelRenderer(ComGfx::IModelRenderer::render_t _renderType, const String & _materialName)
{
    // create model renderer
    ComGfx::IScene * scene = ComGfx::IScene::ScopedExLock(mParentSimulation->getGfx());
    ComGfx::IModelRenderer * modelRenderer = scene->createModelRenderer(getModelRendererName(), _materialName, this);
    modelRenderer->setRenderType(_renderType);
}
//-----------------------------------------------------------------------------
dylab::ComModelTechnique::Instance * dylab::SimObject::createModelTechnique(const String & _modelTechniqueName)
{
    // check first, if the model is in valid state
    DYLAB_ASSERT(mModelStructure.isValid());

    // create the specified model technique
    mModelTechnique = ComModelTechnique::create(_modelTechniqueName);

    // initialize the component
    ComModelTechnique::ScopedExLock(&mModelTechnique)->initialize(this);

    // return the ready-to-use component instance
    return getModelTechnique();
}
//-----------------------------------------------------------------------------
dylab::ComModelIntegrator::Instance * dylab::SimObject::createModelIntegrator(const String & _modelIntegratorName)
{
    // check first, if the model is in valid state
    DYLAB_ASSERT(mModelStructure.isValid());

    // create the specified model integrator
    mModelIntegrator = ComModelIntegrator::create(_modelIntegratorName);

    // initialize the loaded component
    ComModelIntegrator::ScopedExLock(&mModelIntegrator)->initialize(this);

    // return the ready-to-use component instance
    return getModelIntegrator();
}
//-----------------------------------------------------------------------------
void dylab::SimObject::loadFromPropertyTree(const PropertyTree & _pt)
{
    // create & load the structure
    const PropertyTree & modelPT = _pt.getNode("structure")->getChildren();    
	ComModelStructure::loadComFromPropertyTree(createModelStructure(IComponent::loadComName(modelPT)), modelPT);
    
    // create model renderer
    const PropertyTree::Node * rendererNode = _pt.getNode("renderer");
    createModelRenderer(rendererNode->getValue().parse<ComGfx::IModelRenderer::render_t>(),
		rendererNode->getChildNodeValue("material"));
    ComGfx::IModelRenderer::ScopedExLock(getParentSimulation()->getGfx(), getModelRendererName())
        ->setCastShadows(rendererNode->getChildNodeValueT("castShadows", true));
    

    {   // the model is to be transformed
		ComModelStructure::ScopedExLock model(getModelStructure());        

		// first rotate (axis and angle in degrees)
        Vector4 rotation = _pt.getNodeValueT("rotation", Vector4::ZERO);
		if (rotation.w() != Math::ZERO)		
			model->rotate(rotation.getVector3(), Math::toRadians(rotation.w()));		

		// then scale
		Vector3 scale = _pt.getNodeValueT("scale", Vector3::ONE);
		if (scale != Vector3::ONE)
			model->scale(scale);

		// and finally translate
		Vector3 pos = _pt.getNodeValueT("position", Vector3::ZERO);
		if (pos != Vector3::ZERO)
			model->translate(pos);
    }

    // create & load model technique
	const PropertyTree & techniquePT = _pt.getNode("technique")->getChildren();
	ComModelTechnique::loadComFromPropertyTree(createModelTechnique(IComponent::loadComName(techniquePT)), techniquePT);

    // create & load model integrator
	const PropertyTree & integratorPT = _pt.getNode("integrator")->getChildren();
	ComModelIntegrator::loadComFromPropertyTree(createModelIntegrator(IComponent::loadComName(integratorPT)), integratorPT);
}
//-----------------------------------------------------------------------------
void dylab::SimObject::logStatistics()
{
    // log sim object name
    DYLAB_LOGF("SimObject %1%: ", mName);
    
    // log sim object model description
    DYLAB_LOGF("\tmodel: %1%", ComModelStructure::ScopedShLock(&mModelStructure)->getDescription());

    // log sim object technique description
    DYLAB_LOGF("\ttechnique: %1%", ComModelTechnique::ScopedShLock(&mModelTechnique)->getDescription());

    // log sim object integrator description
    DYLAB_LOGF("\tintegrator: %1%", ComModelIntegrator::ScopedShLock(&mModelIntegrator)->getDescription());

    // log sim object gfx description
    DYLAB_LOGF("\trenderer: %1%", ComGfx::IModelRenderer::ScopedShLock(mParentSimulation->getGfx(), getModelRendererName())->getDescription());
}
//-----------------------------------------------------------------------------
void dylab::SimObject::update(real_t _timeStep, real_t * _techniqueTime, real_t * _integratorTime) 
{   
    // for statistics
    Timer timer;

    // technique step
    timer.reset();
    ComModelTechnique::ScopedExLock(&mModelTechnique)->performComputation();
    if (_techniqueTime != NULL)
        *_techniqueTime += timer.getElapsedMiliseconds();                    

    // integrator step
    timer.reset();
    ComModelIntegrator::ScopedExLock(&mModelIntegrator)->timeStep(_timeStep);    
    if (_integratorTime != NULL)
        *_integratorTime += timer.getElapsedMiliseconds();
 }
//-----------------------------------------------------------------------------